﻿#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "appcore.h"
#include "dialog/msgbox.h"
#include "datasimucache.h"
#include "globalstatic.h"
#include "globalsymbol.h"
#include "localcontext.h"
#include "dialog/dlgsystemset.h"
#include "dialog/dlg1553.h"
#include "dialog/dlgmodbusrtu.h"
#include "dialog/dlghistory.h"
#include "dialog/dlgreport.h"
#include "dialog/dlgfault.h"

#include "sdk/QsLog/include/QsLog.h"
#include "sdk/OlsApi/include/OlsApi.h"

#include <QDebug>
#include <QCloseEvent>
#include <QDateTime>
#include <QThread>
#include <QProgressDialog>
#include <QMessageBox>


MainWindow::MainWindow(QSettings* settings, DatabaseInterface* databaseInterface, QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , m_settings(settings)
    , m_databaseInterface(databaseInterface)
{
    ui->setupUi(this);

    // 初始化
    initConfig();
    initUI();
    init();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::keyPressEvent(QKeyEvent *event)
{
    int key = event->key();
    Qt::KeyboardModifiers modifiers = event->modifiers();

    if (modifiers == Qt::ControlModifier || modifiers & Qt::ControlModifier) {
        // Ctrl键
        switch (key) {
        case Qt::Key_Escape:
            break;
        default:
            break;
        }
    }
    if (modifiers == Qt::KeypadModifier) {
        switch (key) {
        case Qt::Key_Enter:
            break;
        default:
            break;
        }
    }
    if (modifiers == Qt::NoModifier) {
        switch (key) {
        case Qt::Key_Return:
            break;
        case Qt::Key_F9:
            // 运行F9, 不在初始位置调整过程中进行
            if (!m_adjusting) {
                onRun();
            }
            break;
        case Qt::Key_F10:
            // 异常终止F10, 不在初始位置调整过程中进行
            if (!m_adjusting) {
                onTerminate();
            }
            break;
        default:
            break;
        }
    }

    QWidget::keyPressEvent(event);
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (m_adjusting) {
        // 如果正在进行初始位置调整, 则不能关闭; 必须等待调整完成
        event->ignore();
    } else if (m_dataSimuThread->isRunning()) {
        // 如果正在数据仿真进行中, 则不能关闭; 必须先停止仿真线程
        event->ignore();
    } else {
        // 如果已经开始了训练考试
//        if (GlobalStatic::training()) {
//            // 考试结束
//            GlobalStatic::setTraining(false);

//            // 考核成绩
//            emit sigCourseScore();
//            // 上报训练结束
//            emit sigTrainEnd();
//        }

        // 停止数据仿真线程
        //if (m_dataSimuThread->isRunning())
        //    m_dataSimuThread->stop();

        // 关闭串口资源
        if (m_serialPort != nullptr)
            m_serialPort->close();

        // 释放Ols动态库资源
#ifdef GPIO
        DeinitializeOls();
#endif

        event->accept();
    }
}

void MainWindow::initConfig()
{
    // JSON配置文件
    m_jsonConfig = new JsonConfig;
    // XML配置文件
    m_xmlConfig = new XmlConfig;
}

void MainWindow::initUI()
{
    m_grayPixmap.load(":res/image/gray.png");
    m_greenPixmap.load(":res/image/green.png");
    ui->lblSerialPortStatus->setPixmap(m_grayPixmap);
    ui->lblSerialPortStatus->setScaledContents(true);
    ui->splitter->setStretchFactor(0, 1);
    ui->splitter->setStretchFactor(1, 3);

    // 操作
    connect(ui->btnOpenSerialPort, &QPushButton::clicked, this, &MainWindow::onOpenSerialPort);
    connect(ui->btnSystemSet, &QPushButton::clicked, this, &MainWindow::onSystemSet);
    connect(ui->btn1553, &QPushButton::clicked, this, &MainWindow::on1553);
    connect(ui->btnRun, &QPushButton::clicked, this, &MainWindow::onRun);
    connect(ui->btnTerminate, &QPushButton::clicked, this, &MainWindow::onTerminate);
    connect(ui->btnDataProcess, &QPushButton::clicked, this, &MainWindow::onDataProcess);
    connect(ui->btnOneKey, &QPushButton::clicked, this, &MainWindow::onOneKey);

    // 加载样式表
    loadStyle(":res/qss/style_Servo.qss");

    // (菜单)选项动作
    createAction();

    // 菜单栏
    initMenu();

    // 流程菜单
    initFlow();

    // 使用布局添加页面
    initForm();

    // 状态栏
    initStatus();

    // 视频播放页面
    initVideo();

    // 定时器
    m_timer = new QTimer(this);
    // 高精度，试图保持ms级别
    //m_timer->setTimerType(Qt::PreciseTimer);
    m_timer->setInterval(500);
    connect(m_timer, &QTimer::timeout, this, &MainWindow::onTimeout);
    //m_timer->start();
}

void MainWindow::initMenu()
{
    m_settings->beginGroup("Servo");
    bool debug = m_settings->value("debug").toBool();
    m_settings->endGroup();

    if (debug) {
        // Debug时启用选项菜单
        QMenu* menuOption = new QMenu(QStringLiteral("选项"), this);
        menuOption->addAction(AppCore::getCore()->getAction("App.Modbus"));
        menuOption->addSeparator();
        menuOption->addAction(AppCore::getCore()->getAction("App.Quit"));
        ui->menubar->addMenu(menuOption);
    }
}

void MainWindow::initFlow()
{
    // 流程菜单
    m_flowModel = new QStandardItemModel;
    // 使用Model设置QTreeView表头
    m_flowModel->setHorizontalHeaderLabels(QStringList(QStringLiteral("名称")));
    // 禁止编辑
    ui->tvwFlow->setEditTriggers(QAbstractItemView::NoEditTriggers);
    // QTreeView关联Model
    ui->tvwFlow->setModel(m_flowModel);
    // 右键菜单
    m_flowContextMenu = new QMenu(ui->tvwFlow);
    m_flowContextMenu->addAction(AppCore::getCore()->getAction("Flow.Run"));
    m_flowContextMenu->addAction(AppCore::getCore()->getAction("Flow.LastPlot"));
    m_flowContextMenu->addAction(AppCore::getCore()->getAction("Flow.QueryHistory"));
    ui->tvwFlow->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->tvwFlow, &QTreeView::customContextMenuRequested, this, &MainWindow::onFlowContextMenuPopup);
    connect(ui->tvwFlow->selectionModel(), &QItemSelectionModel::currentChanged, this, &MainWindow::onFlowItemSelectionChanged);
    connect(m_flowModel, &QStandardItemModel::itemChanged, this, &MainWindow::onFlowItemCheckChanged);
}

void MainWindow::initForm()
{
    // 使用布局添加页面
    m_frmRealtimeData = new FormRealtimeData(m_jsonConfig);
    connect(m_frmRealtimeData, &FormRealtimeData::sigUpdatePlot, this, &MainWindow::onUpdatePlot);
    connect(m_frmRealtimeData, &FormRealtimeData::sigTestingVisible, this, &MainWindow::onTesingVisible);
    QVBoxLayout* realtimeDataLayout = new QVBoxLayout(ui->tabRealtimeData);
    realtimeDataLayout->setContentsMargins(0, 0, 0, 0);
    realtimeDataLayout->addWidget(m_frmRealtimeData);

    m_frmRealtimePlot = new FormRealtimePlot(m_settings, m_jsonConfig);
    QVBoxLayout* realtimePlotLayout = new QVBoxLayout(ui->tabRealtimePlot);
    realtimePlotLayout->setContentsMargins(0, 0, 0, 0);
    realtimePlotLayout->addWidget(m_frmRealtimePlot);

    m_frmChannelCoefficient = new FormChannelCoefficient;
    connect(m_frmChannelCoefficient,  &FormChannelCoefficient::sigUpdateChannelCoefficient, this, &MainWindow::onUpdateChannelCoefficient);
    QVBoxLayout* channelCoefficientLayout = new QVBoxLayout(ui->tabChannelCoefficient);
    channelCoefficientLayout->setContentsMargins(0, 0, 0, 0);
    channelCoefficientLayout->addWidget(m_frmChannelCoefficient);

    m_frmTestInfo = new FormTestInfo;
    QVBoxLayout* testInfoLayout = new QVBoxLayout(ui->tabTestInfo);
    testInfoLayout->setContentsMargins(0, 0, 0, 0);
    testInfoLayout->addWidget(m_frmTestInfo);

    m_frmHelp = new FormHelp;
    QVBoxLayout* helpLayout = new QVBoxLayout(ui->tabHelp);
    helpLayout->setContentsMargins(0, 0, 0, 0);
    helpLayout->addWidget(m_frmHelp);
}

void MainWindow::initStatus()
{
    m_lblTesting = new QLabel;
    m_lblTesting->setObjectName("lblTesting");
    ui->statusbar->addPermanentWidget(m_lblTesting);

    // 状态栏测试信息
    m_testingVisible = false;
}

void MainWindow::initVideo()
{
    m_settings->beginGroup("Video");
    m_videoVisible = m_settings->value("visible").toBool();
    m_settings->endGroup();

    m_videoWindow = nullptr;
    if (m_videoVisible) {
        m_videoWindow = new VideoWindow(m_settings);

        // 屏幕数量, 多个屏幕时放置到副屏
        int screencount = QApplication::desktop()->screenCount();
        if (screencount > 1) {
            QList<QScreen*> screenList = QGuiApplication::screens();
            QScreen* screen = screenList.at(1);
            // 返回屏幕工作区的中心点坐标, 移动到副屏中心
            QPoint centerPoint = screen->availableGeometry().center();
            m_videoWindow->move(centerPoint.x() - m_videoWindow->width() / 2, centerPoint.y() - m_videoWindow->height() / 2);
        }

        //m_videoWindow->show();
    }
}

void MainWindow::initGPIO()
{
    // 复位按键, 复位封锁按键
    m_gpioReset = m_jsonConfig->gpioReset();
    m_gpioResetLatch = m_jsonConfig->gpioResetLatch();

#ifdef GPIO
    if(!InitializeOls()) {
        QLOG_ERROR() <<  "Error InitializeOls()!!" << GetDllStatus();;
    }

    BYTE PortVal = 0;

    WriteIoPortByte(0x2e,0x87); // SetPortVal(0x2e, 0x87, 1);
    WriteIoPortByte(0x2e,0x87); // SetPortVal(0x2e, 0x87, 1);

    WriteIoPortByte(0x2e,0x07); // SetPortVal(0x2e, 0x07, 1);
    WriteIoPortByte(0x2f,0x06); //  SetPortVal(0x2f, 0x06, 1);

    WriteIoPortByte(0x2e,0x27); //  SetPortVal(0x2e, 0x27, 1);
    PortVal = ReadIoPortByte(0x2f);//  GetPortVal(0x2f, &PortVal,1);
    //WriteIoPortByte(0x2f,(PortVal &0xf3));//  SetPortVal(0x2f, (PortVal &0xf3), 1);//bit2,3=00,
    WriteIoPortByte(0x2f,(PortVal &0xf3));//bit2,3=00, bit0=00

    WriteIoPortByte(0x2e,0x28); //  SetPortVal(0x2e, 0x28, 1);
    PortVal = ReadIoPortByte(0x2f);//  GetPortVal(0x2f, &PortVal,1);
    WriteIoPortByte(0x2f,(PortVal |0x20));  //

    WriteIoPortByte(0x2e,0x2B); //  SetPortVal(0x2e, 0x28, 1);
    PortVal = ReadIoPortByte(0x2f);//  GetPortVal(0x2f, &PortVal,1);
    WriteIoPortByte(0x2f,(PortVal &0xFE));  //0x2B bit0=00;

    // GP72 input
    WriteIoPortByte(0x2e,0x80);//D20160726+
    PortVal = ReadIoPortByte(0x2f);//GetPortVal(0x2f, &PortVal, 1);
    WriteIoPortByte(0x2f,  (PortVal & 0xfb));

    // GP74 input, 这个必须添加(必须按键之后才能正确收到074 input的状态变化)
    WriteIoPortByte(0x2e,0x80);//D20160726+
    PortVal = ReadIoPortByte(0x2f);	        //GetPortVal(0x2f, &PortVal,1);
    WriteIoPortByte(0x2f,  (PortVal &0xef));
#endif
}

void MainWindow::init()
{
    // 是否正在进行初始位置调整
    m_adjusting = false;

    // 串口打开标识
    m_opened = false;

    // 仿真时间间隔: ms
    m_settings->beginGroup("Simu");
    m_simuInterval = m_settings->value("interval").toInt();
    m_settings->endGroup();
    m_frmTestInfo->setSimuInterval(m_simuInterval);
    // 已采集点数
    m_simuPoints = 0;
    // 理论运行时间
    m_theoryRunTime = 0;


    // 数据库压缩体积
//    bool ret = m_databaseInterface->vacuumSqlite();
//    if (!ret) {
//        QLOG_ERROR() << "vaccum in database error";
//    }

    // 查询仿真描述列表
    QList<ChannelDescription> channelDescriptionList;
    bool ret = m_databaseInterface->getChannelDescriptionList(channelDescriptionList);
    if (!ret) {
        QLOG_ERROR() << "query channel description list from database error";
    }

    // 初始化通道系数
    QList<double> coefficientList;
    for (int i = 0; i < channelDescriptionList.size(); i++)
        coefficientList << channelDescriptionList.at(i).coefficient;
    m_frmRealtimePlot->setChannelCoefficient(coefficientList);
    m_frmChannelCoefficient->setChannelCoefficient(coefficientList);

    // 初始化仿真数据缓存
    QStringList idList;
    for (int i = 0; i < channelDescriptionList.size(); i++)
        idList << channelDescriptionList.at(i).id;
    DataSimuCache::initialize(idList);

    // 查询报表行描述列表
    QList<ReportLine> reportLineList;
    ret = m_databaseInterface->getReportLineList(reportLineList);
    if (!ret) {
        QLOG_ERROR() << "query report line list from database error";
    }

    // 查询电机指令列表
    QList<Cmd> cmdList;
    ret = m_databaseInterface->getCmdList(cmdList);
    if (!ret) {
        QLOG_ERROR() << "query cmd list from database error";
    }
    // 初始化电机指令
    GlobalStatic::initCmd(cmdList);

    // 查询训练步骤列表
    QList<TrainStep> trainStepList;
    ret = m_databaseInterface->getTrainStepList(trainStepList);
    if (!ret) {
        QLOG_ERROR() << "query train step list from database error";
    }
    // 初始化训练步骤
    GlobalStatic::initTrainStep(trainStepList);


    // Modbus485 RTU串口
    m_serialPort = new SerialPort(m_settings);

    // UDP通信线程
    m_udpServer = new UdpServer(m_settings);
    QThread* udpServerThread = new QThread;
    connect(udpServerThread, &QThread::finished, m_udpServer, &QObject::deleteLater);
    connect(this, &MainWindow::sigUdpListen, m_udpServer, &UdpServer::onListen);
    connect(this, &MainWindow::sigHeartBeat, m_udpServer, &UdpServer::onHeartBeat);
    connect(this, &MainWindow::sigTrainEnd, m_udpServer, &UdpServer::onTrainEnd);
    connect(this, &MainWindow::sigStepNotify, m_udpServer, &UdpServer::onStepNotify);
    connect(this, &MainWindow::sigFaultNotify, m_udpServer, &UdpServer::onFaultNotify);
    connect(this, &MainWindow::sigCourseScore, m_udpServer, &UdpServer::onCourseScore);
    connect(m_udpServer, &UdpServer::sigCourseTrain, this, &MainWindow::onCourseTrain);
    m_udpServer->moveToThread(udpServerThread);
    udpServerThread->start();

    // UDP绑定端口进行监听
    QTimer::singleShot(200, this, [=] {emit sigUdpListen();});

    // 数据仿真线程
    m_dataSimuThread = new DataSimuThread(m_settings, m_jsonConfig);
    m_dataSimuThread->setChannelDescriptionList(channelDescriptionList);
    connect(m_dataSimuThread, &DataSimuThread::sigTimeReady, m_frmRealtimeData, &FormRealtimeData::onRefreshData);
    connect(m_dataSimuThread, &DataSimuThread::sigTimeReady, m_frmRealtimePlot, &FormRealtimePlot::onRefreshPlot);
    connect(m_dataSimuThread, &DataSimuThread::sigTimeReady, this, &MainWindow::onTesting);
    connect(m_dataSimuThread, &DataSimuThread::sigFaultOccurred, this, &MainWindow::onFaultOccurred);
    connect(m_dataSimuThread, &DataSimuThread::sigStepWait, this, &MainWindow::onStepWait);
    connect(m_dataSimuThread, &DataSimuThread::sigSendCmd, this, &MainWindow::onSendCmd);
    connect(m_dataSimuThread, &DataSimuThread::sigSubflowFinished, this, &MainWindow::onSubflowFinished);
    connect(m_dataSimuThread, &DataSimuThread::sigCompleteFinished, this, &MainWindow::onCompleteFinished);
    connect(m_dataSimuThread, &DataSimuThread::sigStepNotify, this, &MainWindow::onStepNotify);
    connect(m_dataSimuThread, &DataSimuThread::sigStepNotify, m_udpServer, &UdpServer::onStepNotify);
    //m_dataSimuThread->start();

    // 数据存盘线程
    m_dataSimuStorage = new DataSimuStorage(m_settings, channelDescriptionList, reportLineList);
    QThread* storageThread = new QThread;
    connect(storageThread, &QThread::finished, m_dataSimuStorage, &QObject::deleteLater);
    connect(m_dataSimuStorage, &DataSimuStorage::sigErrorOccurred, this, &MainWindow::onErrorOccurred);
    connect(this, &MainWindow::sigOpenStorage, m_dataSimuStorage, &DataSimuStorage::onOpen);
    connect(this, &MainWindow::sigCloseStorage, m_dataSimuStorage, &DataSimuStorage::onClose);
    connect(m_dataSimuThread, &DataSimuThread::sigStorage, m_dataSimuStorage, &DataSimuStorage::onStorage);
    m_dataSimuStorage->moveToThread(storageThread);
    storageThread->start();
}

void MainWindow::createAction()
{
    QAction *action = nullptr;
    action = new QAction(QStringLiteral("Modbus"), nullptr);
    if(action) {
        connect(action, &QAction::triggered, this, [=]() {
            DlgModbusRtu dlgModbusRtu(m_settings, m_serialPort);
            if (dlgModbusRtu.exec() == QDialog::Accepted) {
            }
        });
        AppCore::getCore()->insertAction("App.Modbus", action);
    }
    action = new QAction(QStringLiteral("退出"), nullptr);
    if(action) {
        connect(action, &QAction::triggered, this, [=](){ qApp->quit(); });
        AppCore::getCore()->insertAction("App.Quit", action);
    }

    action = new QAction( QStringLiteral("运行"), nullptr);
    if(action) {
        connect(action, &QAction::triggered, this, &MainWindow::onRun);
        AppCore::getCore()->insertAction("Flow.Run", action);
    }
    action = new QAction( QStringLiteral("最后一次原始曲线"), nullptr);
    if(action) {
        connect(action, &QAction::triggered, this, &MainWindow::onLastPlot);
        AppCore::getCore()->insertAction("Flow.LastPlot", action);
    }
    action = new QAction( QStringLiteral("查询历史数据"), nullptr);
    if(action) {
        connect(action, &QAction::triggered, this, &MainWindow::onQueryHistory);
        AppCore::getCore()->insertAction("Flow.QueryHistory", action);
    }
}

void MainWindow::loadStyle(const QString &stylePath)
{
    QFile styleFile(stylePath);
    QTextStream styleStream(&styleFile);
    if(!styleFile.open(QIODevice::ReadOnly)) {
        QLOG_ERROR() << "style file can not open";
        return;
    }

    QString style = styleStream.readAll();
    this->setStyleSheet(style);
    styleFile.close();
}

bool MainWindow::loadFlow(int level)
{
    if (m_xmlConfig->containFlow(level)) {
        // 创建根节点, 抽象item, 并没有实际数据
        QStandardItem* tvwRootItem = m_flowModel->invisibleRootItem();
        // 创建并添加item的子节点: flow
        Flow flow = m_xmlConfig->flow(level);
        QStandardItem* flowItem = new QStandardItem(flow.name);
        flowItem->setData(flow.level);
        flowItem->setCheckable(true);
        flowItem->setCheckState(Qt::Checked);
        tvwRootItem->appendRow(flowItem);

        // 创建并添加item的子节点: subflow0
        QList<Subflow> subflow0List = m_xmlConfig->subflow0List(level);
        for (int i = 0; i < subflow0List.size(); i++) {
            Subflow subflow0 = subflow0List.at(i);
            QStandardItem* subflow0Item = new QStandardItem(subflow0.name);
            QMap<QString, QVariant> subflow0Map;
            subflowToMap(subflow0, subflow0Map);
            subflow0Item->setData(subflow0Map);
            subflow0Item->setCheckable(true);
            subflow0Item->setCheckState(Qt::Checked);
            // 向flowItem子节点添加子节点数据
            flowItem->appendRow(subflow0Item);

            // 创建并添加Item的子节点: subflow1
            QList<Subflow> subflow1List = m_xmlConfig->subflow1List(subflow0.id);
            for (int j = 0; j < subflow1List.size(); j++) {
                Subflow subflow1 = subflow1List.at(j);
                QStandardItem* subflow1Item = new QStandardItem(subflow1.name);
                QMap<QString, QVariant> subflow1Map;
                subflowToMap(subflow1, subflow1Map);
                subflow1Item->setData(subflow1Map);
                subflow1Item->setCheckable(true);
                subflow1Item->setCheckState(Qt::Checked);
                // 向subflow0Item子节点添加子节点数据
                subflow0Item->appendRow(subflow1Item);

                // 创建并添加Item的子节点: subflow2
                QList<Subflow> subflow2List = m_xmlConfig->subflow2List(subflow1.id);
                for (int k = 0; k < subflow2List.size(); k++) {
                    Subflow subflow2 = subflow2List.at(k);
                    QStandardItem* subflow2Item = new QStandardItem(subflow2.name);
                    QMap<QString, QVariant> subflow2Map;
                    subflowToMap(subflow2, subflow2Map);
                    subflow2Item->setData(subflow2Map);
                    subflow2Item->setCheckable(true);
                    subflow2Item->setCheckState(Qt::Checked);
                    // 向subflow1Item子节点添加子节点数据
                    subflow1Item->appendRow(subflow2Item);

                    // 创建并添加Item的子节点: subflow3
                    QList<Subflow> subflow3List = m_xmlConfig->subflow3List(subflow2.id);
                    for (int l = 0; l < subflow3List.size(); l++) {
                        Subflow subflow3 = subflow3List.at(l);
                        QStandardItem* subflow3Item = new QStandardItem(subflow3.name);
                        QMap<QString, QVariant> subflow3Map;
                        subflowToMap(subflow3, subflow3Map);
                        subflow3Item->setData(subflow3Map);
                        subflow3Item->setCheckable(true);
                        subflow3Item->setCheckState(Qt::Checked);
                        // 向subflow2Item子节点添加子节点数据
                        subflow2Item->appendRow(subflow3Item);
                    }
                }
            }
        }

        return true;
    }

    return false;
}

void MainWindow::subflowToMap(const Subflow &subflow, QMap<QString, QVariant> &subflowMap)
{
    subflowMap.insert("id", subflow.id);
    subflowMap.insert("name", subflow.name);
    subflowMap.insert("operation", subflow.operation);
}

void MainWindow::traverseSubflowParent(QStandardItem *item, QStringList &nameList)
{
    while (item != nullptr) {
        nameList << item->text();

        item = item->parent();
    }
}

void MainWindow::traverseSubflowChildren(QStandardItem *item, QList<Subflow> &subflowList)
{
    if (item == nullptr)
        return;

    if (item->checkState() == Qt::Checked) {
        QMap<QString, QVariant> map = item->data().toMap();
        Subflow subflow;
        subflow.id = map.value("id").toInt();
        subflow.name = map.value("name").toString();
        subflow.operation = map.value("operation").toInt();
        subflowList << subflow;
    }

    for (int i = 0; i < item->rowCount(); i++) {
        QStandardItem* childItem = item->child(i);
        if (childItem->hasChildren()) {
            traverseSubflowChildren(childItem, subflowList);
        } else {
            if (childItem->checkState() == Qt::Checked) {
                QMap<QString, QVariant> chileMap = childItem->data().toMap();
                Subflow subflow;
                subflow.id = chileMap.value("id").toInt();
                subflow.name = chileMap.value("name").toString();
                subflow.operation = chileMap.value("operation").toInt();
                subflowList << subflow;
            }
        }
    }
}

void MainWindow::traverseSubflowChildren(QStandardItem *item, bool check)
{
    if (item == nullptr)
        return;

    for (int i = 0; i < item->rowCount(); i++) {
        QStandardItem* childItem = item->child(i);
        childItem->setCheckState(check ? Qt::Checked : Qt::Unchecked);
        if (childItem->hasChildren())
            traverseSubflowChildren(childItem, check);
    }
}

void MainWindow::adjustPosition2Center(bool waitCommunication, bool runZero)
{
    // 标记正在进行初始位置调整, 同时禁用操作按钮
    m_adjusting = true;
    AppCore::getCore()->getAction("Flow.Run")->setEnabled(false);
    ui->btnSystemSet->setEnabled(false);
    ui->btn1553->setEnabled(false);
    ui->btnRun->setEnabled(false);
    ui->btnTerminate->setEnabled(false);
    ui->btnDataProcess->setEnabled(false);
    ui->btnOneKey->setEnabled(false);

    // RS485的通讯正常需要等待一段时间, 暂定2s
    if (waitCommunication) {
        // 延时200ms
        QEventLoop eventloop;
        QTimer::singleShot(200, &eventloop, &QEventLoop::quit);
        eventloop.exec();

        // 先发1次读取硬件版本号, 因为首先发的指令在重新加电后不被响应
        QString hardwareVersionCmd = m_jsonConfig->hardwareVersionCmd();
        onSendCmd(hardwareVersionCmd);

        QTimer::singleShot(200, &eventloop, &QEventLoop::quit);
        eventloop.exec();
    }


    // 调整电机到中心位置: 先执行回原点5s(K6: 反向, 速度400rpm; 立即停止, 二次回原点速度0); 然后执行绝对位置44800  7.5s(K12, 默认的中心位置)
    if (runZero) {
        QString zeroCmd = m_jsonConfig->zeroCmd();
        onSendCmd(zeroCmd);

        // 5s
        for(int i = 0; i < 10; i++) {
            int percent = QRandomGenerator::global()->bounded(i * 10, (i + 1) * 10);
            m_lblTesting->setText(QStringLiteral("正在进行电机回原点:%1%").arg(percent));

            // 延时
            QEventLoop eventloop;
            QTimer::singleShot(500, &eventloop, &QEventLoop::quit);
            eventloop.exec();
        }
    }

    QString centerCmd = m_jsonConfig->centerCmd();
    onSendCmd(centerCmd);

    // 4s
    for(int i = 0; i < 10; i++) {
        int percent = QRandomGenerator::global()->bounded(i * 10, (i + 1) * 10);
        m_lblTesting->setText(QStringLiteral("正在进行电机移位中心:%1%").arg(percent));

        // 延时
        QEventLoop eventloop;
        QTimer::singleShot(400, &eventloop, &QEventLoop::quit);
        eventloop.exec();
    }
    m_lblTesting->setText("");

    if (waitCommunication) {
        // 确认一下位置是否在中心, 如果不在中心, 强制退出
        QString realtimePositionCmd = m_jsonConfig->realtimePositionCmd();
        onSendCmd(realtimePositionCmd);

        // 延时200ms
        QEventLoop eventloop;
        QTimer::singleShot(200, &eventloop, &QEventLoop::quit);
        eventloop.exec();

        QByteArray recvBuffer = m_serialPort->recvBuffer();
        if (recvBuffer.size() >= 9) {
            int centerPosition = ((recvBuffer.at(5) & 0xFF) << 24) | ((recvBuffer.at(6) & 0xFF) << 16) | ((recvBuffer.at(3) & 0xFF) << 8) | (recvBuffer.at(4) & 0xFF);
            if ( std::abs(m_jsonConfig->centerPosition() - centerPosition) > ROTOR_CIRCLE ) {
                QLOG_ERROR() << QStringLiteral("启动时检测到电机未移位到中心, 应用程序退出!");
                QMessageBox::critical(this, QStringLiteral("错误"), QStringLiteral("检测到电机未移位到中心, 应用程序退出!"));
                this->close();
            }
        }
    }

    // 标记初始位置调整完成, 同时启用操作按钮
    m_adjusting = false;
    AppCore::getCore()->getAction("Flow.Run")->setEnabled(true);
    ui->btnSystemSet->setEnabled(true);
    ui->btn1553->setEnabled(true);
    ui->btnRun->setEnabled(true);
    ui->btnTerminate->setEnabled(false);
    ui->btnDataProcess->setEnabled(true);
    ui->btnOneKey->setEnabled(true);
}

int MainWindow::readGPIO7xLevel(int gpnum)
{
#ifdef GPIO
    // 读取GPIO 72 input
    BYTE PinStatus = 0;

    WriteIoPortByte(0x2e,0x82);
    PinStatus = ReadIoPortByte(0x2f);
    PinStatus = ( ( PinStatus>>(gpnum - 70) ) & 0x1 );

    return PinStatus;
#else
    return gpnum;
#endif
}

void MainWindow::onTimeout()
{
#ifdef GPIO
    int resetStatus = readGPIO7xLevel(m_gpioReset);
    int resetLatchStatus = readGPIO7xLevel(m_gpioResetLatch);
    //qDebug() << "******" << QTime::currentTime().toString("HH:mm:ss.zzz") << resetStatus << resetLatchStatus;

    // 当从0 -> 1变化时, 说明按下了复位; 当从1 -> 0变化时, 说明抬起了复位
    if (resetStatus > GlobalStatic::getGPIOResetStatus()) {
        if (resetLatchStatus == 0 && m_dataSimuThread != nullptr)
            m_dataSimuThread->assignReset(true);
    }
    if (resetStatus < GlobalStatic::getGPIOResetStatus()) {
        if (resetLatchStatus == 0 && m_dataSimuThread != nullptr)
            m_dataSimuThread->assignReset(false);
    }

    // 记录复位的GPIO的状态值
    GlobalStatic::setGPIOResetStatus(resetStatus);
#endif

    if (GlobalStatic::training()) {
        // 训练考试进行时间
        m_frmTestInfo->updateTraining();
    }

    // 心跳
    //emit sigHeartBeat();
}

void MainWindow::onOpenSerialPort()
{
    if (m_opened) {
        ui->lblSerialPortStatus->setPixmap(m_grayPixmap);
        ui->btnOpenSerialPort->setText(QStringLiteral("打开串口"));

        m_serialPort->close();

        m_opened = false;
    } else {
        if (m_serialPort->open()) {
            ui->lblSerialPortStatus->setPixmap(m_greenPixmap);
            ui->btnOpenSerialPort->setText(QStringLiteral("关闭串口"));
            m_opened = true;
        } else {
            QMessageBox::critical(this, QStringLiteral("错误"), QStringLiteral("串口打开失败!"));
        }
    }
}

void MainWindow::onSystemSet()
{
    // 系统设置
    DlgSystemSet dlgSystemSet(m_settings);
    if (dlgSystemSet.exec() == QDialog::Accepted) {
        // 实时数据清零
        m_frmRealtimeData->clear();
        // 实时曲线清空
        m_frmRealtimePlot->clear();
        // 流程进度置零
        m_frmTestInfo->clear();
        // 状态栏清空
        m_lblTesting->clear();
        ui->statusbar->clearMessage();
        // 已采集点数
        m_simuPoints = 0;
        // 理论运行时间
        m_theoryRunTime = 0;
    }
}

void MainWindow::on1553()
{
    if (m_level == 1) {
        // 操作上报
        GlobalStatic::assignTrainStep("C1510101001");
        emit sigStepNotify("C1510101001");
        m_frmTestInfo->updateStepInfo("C1510101001");
    }
    if (m_level == 2) {
        // 操作上报
        GlobalStatic::assignTrainStep("C1510201001");
        emit sigStepNotify("C1510201001");
        m_frmTestInfo->updateStepInfo("C1510201001");
    }

    // 1553
    Dlg1553 dlg1553(m_settings, m_level);
    connect(&dlg1553, &Dlg1553::sigFaultOccurred, this, &MainWindow::onFaultOccurred);
    if (dlg1553.exec() == QDialog::Accepted) {
    }
}

void MainWindow::onRun()
{
    // 判断数据仿真线程是否已经启动
    if (m_dataSimuThread->isRunning()) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("正在运行过程中!"));
        return;
    }

    // 获取当前选中的树形节点
    QModelIndex modelIndex = ui->tvwFlow->currentIndex();
    QStandardItem* currentItem = m_flowModel->itemFromIndex( modelIndex );
    if (currentItem == nullptr) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("未选中任何流程节点!"));
        return;
    }
    //QString currentItemId = currentItem->data().toMap().value("id").toString();

    // 找出节点及子节点的测试流程步骤, 添加到数据仿真中
    QList<Step> stepList;
    QList<Subflow> subflowList;
    traverseSubflowChildren(currentItem, subflowList);
    for (int i = 0; i < subflowList.size(); i++) {
        int operationId = subflowList.at(i).operation;
        if (operationId > 0 && m_xmlConfig->containOperation(operationId)) {
            stepList << m_xmlConfig->stepList(operationId);
        }
    }
    m_dataSimuThread->setStepList(currentItem->text(), stepList);

    if (QString::compare(currentItem->text(), QStringLiteral("实时监测(不存盘)")) != 0) {
        // 打开存储信号通知
        emit sigOpenStorage();
    }

    // 控制显示操作的复选框状态不可用
    m_frmRealtimeData->changeShowOperation(false);
    // 如果曲线有数据, 清空数据
    m_frmRealtimePlot->clear(true);

    // 计算数据仿真的理论时间
    m_theoryRunTime = 0;
    for (int i = 0; i < stepList.size(); i++) {
        m_theoryRunTime += stepList.at(i).timeline;
    }
    // 已采集点数
    m_simuPoints = 0;
    m_frmTestInfo->setTheroyRunTime(m_theoryRunTime);
    if (m_testingVisible) {
        QString testing = QStringLiteral("已采集点数:0;总点数:%1;当前项目运行时间:0;当前项目理论运行时间:%2").arg(m_theoryRunTime / m_simuInterval).arg(QString::number(m_theoryRunTime / 1000., 'f', 3));
        m_lblTesting->setText(testing);
    }

    // 启动数据仿真线程
    if (!m_dataSimuThread->isRunning())
        m_dataSimuThread->start();

    // 标记使能状态
    AppCore::getCore()->getAction("Flow.Run")->setEnabled(false);
    ui->btnRun->setEnabled(false);
    ui->btnTerminate->setEnabled(true);
}

void MainWindow::onTerminate()
{
    // 关闭存储信号通知
    emit sigCloseStorage();

    // 控制显示操作的复选框状态可用
    m_frmRealtimeData->changeShowOperation(true);
    // 如果显示视频播放窗口, 暂停
    if (m_videoVisible) {
        m_videoWindow->pause(true);
    }

    // 停止数据仿真线程
    if (m_dataSimuThread->isRunning()) {
        m_dataSimuThread->stop();

        if (m_opened) {
            // 调整电机到中心位置: 不用等待通讯连接, 不用先回原点
            adjustPosition2Center(false, false);
        }
    }

    // 标记使能状态
    AppCore::getCore()->getAction("Flow.Run")->setEnabled(true);
    ui->btnRun->setEnabled(true);
    ui->btnTerminate->setEnabled(false);
}

void MainWindow::onDataProcess()
{
    // 判断数据仿真线程是否已经启动
    if (m_dataSimuThread->isRunning()) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("正在运行过程中!"));
        return;
    }

    // 获取当前选中的树形节点
    QModelIndex modelIndex = ui->tvwFlow->currentIndex();
    QStandardItem* currentItem = m_flowModel->itemFromIndex( modelIndex );
    if (currentItem == nullptr) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("未选中任何流程节点!"));
        return;
    }
    QString currentItemId = currentItem->data().toMap().value("id").toString();
    if ( !(currentItemId.startsWith("11") || currentItemId.startsWith("12") || currentItemId.startsWith("21") || currentItemId.startsWith("22")) ) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("请选中伺服机构测试/伺服系统测试节点!"));
        return;
    }

    // 根据路径判断报表文件是否存在
    m_settings->beginGroup("Storage");
    QString reportPath = m_settings->value("reportPath").toString();
    QString history = m_settings->value("history").toString();
    m_settings->endGroup();
    QString reportFileName = QString::fromUtf8("%1%2/report_%3.csv").arg(qApp->applicationDirPath(), reportPath, history);
    //qDebug() << "**********reportFileName: " << reportFileName;

    QFileInfo reportFileInfo(reportFileName);
    if(!reportFileInfo.isFile()) {
        QMessageBox::critical(this, QStringLiteral("提示"), QStringLiteral("数据文件不存在!"));
        return;
    }

    // 测试数据报表
    DlgReport dlgReport(m_settings, m_jsonConfig, currentItemId, reportFileName);
    if (dlgReport.exec() == QDialog::Accepted) {
    }
}

void MainWindow::onOneKey()
{
    // 判断数据仿真线程是否已经启动
    if (m_dataSimuThread->isRunning()) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("正在运行过程中!"));
        return;
    }

    // 获取当前选中的树形节点
    QModelIndex modelIndex = ui->tvwFlow->currentIndex();
    QStandardItem* currentItem = m_flowModel->itemFromIndex( modelIndex );
    if (currentItem == nullptr) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("未选中任何流程节点!"));
        return;
    }
    QString currentItemId = currentItem->data().toMap().value("id").toString();
    if ( !(currentItemId.startsWith("11") || currentItemId.startsWith("12") || currentItemId.startsWith("21") || currentItemId.startsWith("22")) ) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("请选中伺服机构测试或伺服系统测试节点!"));
        return;
    }

    // 根据时间找到最新的数据文件
    m_settings->beginGroup("Storage");
    QString reportPath = m_settings->value("reportPath").toString();
    QString lastest = m_settings->value("lastest").toString();
    m_settings->endGroup();
    QString reportFileName = QString::fromUtf8("%1%2/report_%3.csv").arg(qApp->applicationDirPath(), reportPath, lastest);
    //qDebug() << "**********reportFileName: " << reportFileName;

    QFileInfo reportFileInfo(reportFileName);
    if (!reportFileInfo.exists()) {
        QMessageBox::critical(this, QStringLiteral("错误"), QStringLiteral("数据文件不存在!"));
        return;
    }

#if 0
    // 显示7.5s的进度条
    QProgressDialog progressDialog;
    progressDialog.setWindowModality(Qt::WindowModal);
    progressDialog.setLabelText(QStringLiteral("正在一键处理..."));
    progressDialog.setRange(0, 100);
    for(int i = 0; i < 10; i++) {
        if(progressDialog.wasCanceled())
            break;
        progressDialog.setValue(QRandomGenerator::global()->bounded(i * 10, (i + 1) * 10));

        // 延时
        QEventLoop eventloop;
        QTimer::singleShot(750, &eventloop, &QEventLoop::quit);
        eventloop.exec();
    }
    progressDialog.setValue(100);
#endif

    // 测试数据报表
    DlgReport dlgReport(m_settings, m_jsonConfig, currentItemId, reportFileName);
    if (dlgReport.exec() == QDialog::Accepted) {
    }
}

void MainWindow::onFlowContextMenuPopup(const QPoint &pos)
{
    m_flowContextMenu->exec(ui->tvwFlow->mapToGlobal(pos));
}

void MainWindow::onFlowItemSelectionChanged(const QModelIndex &current, const QModelIndex &previous)
{
    Q_UNUSED(previous);

    QModelIndex modelIndex = current.sibling(current.row(), 0);
    QStandardItem* item = m_flowModel->itemFromIndex(modelIndex);
    if (item == nullptr)
        return;

    // 流程进度和状态栏
    QString currentItemName;
    QStringList nameList;
    traverseSubflowParent(item, nameList);
    for (int i = nameList.size() - 1; i >= 0; i--) {
        currentItemName.append(nameList.at(i));
        if (i > 0)
            currentItemName.append(": ");
    }
    m_frmTestInfo->setItemName(currentItemName);
    ui->statusbar->showMessage(currentItemName);
}

void MainWindow::onFlowItemCheckChanged(QStandardItem* item)
{
    if (item == nullptr)
        return;

    if (item->isCheckable()) {
        // 遍历子节点, 是否选中状态
        traverseSubflowChildren(item, item->checkState() == Qt::Checked);
    }
}

void MainWindow::onLastPlot()
{
    // 判断数据仿真线程是否已经启动
    if (m_dataSimuThread->isRunning()) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("正在运行过程中,请等待测试流程完成!"));
        return;
    }

    // 查询仿真描述列表
    QList<ChannelDescription> channelDescriptionList;
    bool ret = m_databaseInterface->getChannelDescriptionList(channelDescriptionList);
    if (!ret) {
        QLOG_ERROR() << "query channel description list from database in last plot error";
    }

    m_frmRealtimePlot->showLastPlot(channelDescriptionList);
}

void MainWindow::onQueryHistory()
{
    // 判断数据仿真线程是否已经启动
    if (m_dataSimuThread->isRunning()) {
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("正在运行过程中,请等待测试流程完成!"));
        return;
    }

    // 查询仿真描述列表
    QList<ChannelDescription> channelDescriptionList;
    bool ret = m_databaseInterface->getChannelDescriptionList(channelDescriptionList);
    if (!ret) {
        QLOG_ERROR() << "query channel description list from database in query history error";
    }

    DlgHistory dlgHistory(m_settings, channelDescriptionList);
    if (dlgHistory.exec() == QDialog::Accepted) {
        MsgBox msgBox(QStringLiteral("查询历史数据完成"));
        msgBox.setButtonText(QStringLiteral("确定"));
        if (msgBox.exec() == QDialog::Accepted) {
        }
    }
}

void MainWindow::onErrorOccurred(QString error)
{
    Q_UNUSED(error);
}

void MainWindow::onPrepare()
{
    ui->btnOpenSerialPort->setVisible(false);
    if (m_serialPort->open()) {
        ui->lblSerialPortStatus->setPixmap(m_greenPixmap);
        m_opened = true;

        // 调整电机到中心位置: 先回原点, 再到中心
        //adjustPosition2Center(true, true);
    } else {
        ui->lblSerialPortStatus->setPixmap(m_grayPixmap);
        m_opened = false;
        QMessageBox::StandardButton button = QMessageBox::question(this, QStringLiteral("错误"), QStringLiteral("通讯端口打开失败,将不会驱动电机运行,是否继续?"), QMessageBox::Yes | QMessageBox::No);
        if (button == QMessageBox::No)
            qApp->quit();
    }
}

void MainWindow::onPrepareShow()
{
    // 测试级别
    m_settings->beginGroup("Servo");
    m_level = m_settings->value("level").toInt();
    m_settings->endGroup();
    //qDebug() << "****level: " << level;

    if (loadFlow(m_level)) {
        // 展开QTreeView
        ui->tvwFlow->expandToDepth(0);
        this->show();
    } else {
        QLOG_ERROR() << QStringLiteral("测试流程加载失败, 应用程序退出!");
        QMessageBox::critical(this, QStringLiteral("错误"), QStringLiteral("测试流程加载失败, 应用程序退出!"));
        qApp->quit();
    }

    // 训练步骤标记为没有进行
    GlobalStatic::initializeTrainStep(m_level);

    // 视频播放窗口, 置于主窗口之后显示
    if (m_videoVisible) {
        m_videoWindow->showMaximized();
        this->raise();
        this->activateWindow();
    }

    // 初始化Ols动态库
    initGPIO();

    // 延时1.2s, 启动定时器
    QEventLoop eventloop;
    QTimer::singleShot(1200, &eventloop, &QEventLoop::quit);
    eventloop.exec();

    m_timer->start();
}

void MainWindow::onUpdatePlot(const QStringList idList)
{
    // 查询仿真数据列表
    QList<ChannelDescription> channelDescriptionList;
    bool ret = m_databaseInterface->getChannelDescriptionList(channelDescriptionList);
    if (!ret) {
        QLOG_ERROR() << "query channel description list when add plots from database error";
        return;
    }

    QList<QPair<QString, QString>> pairList;
    for (int i = 0; i < channelDescriptionList.size(); i++) {
        if (idList.contains(channelDescriptionList.at(i).id))
            pairList << qMakePair(channelDescriptionList.at(i).id, channelDescriptionList.at(i).name);
    }

    m_frmRealtimePlot->updatePlot(pairList);
}

void MainWindow::onTesingVisible(bool visible)
{
    // 状态栏测试信息
    m_testingVisible = visible;
}

void MainWindow::onTesting()
{
    // 计算流程进度
    m_simuPoints++;
    qint64 runTime = QDateTime::currentMSecsSinceEpoch() - GlobalStatic::getZeroTimeStamp();
    m_frmTestInfo->updateProgress(m_simuPoints, runTime);
    if (m_testingVisible) {
        int totalPoints = m_theoryRunTime / m_simuInterval;
        QString testing = QStringLiteral("已采集点数:%1;总点数:%2;当前项目运行时间:%3;当前项目理论运行时间:%4").arg(m_simuPoints).arg(totalPoints)
                                                                                                     .arg(QString::number(runTime / 1000., 'f', 3), QString::number(m_theoryRunTime / 1000., 'f', 3));
        m_lblTesting->setText(testing);
    }
}

void MainWindow::onUpdateChannelCoefficient(const QList<double> &coefficientList)
{
    bool ret = m_databaseInterface->updateChannelCoefficient(coefficientList);
    if (ret) {
        // 数据仿真线程更新通道系数
        m_frmRealtimePlot->setChannelCoefficient(coefficientList);
        QMessageBox::information(this, QStringLiteral("提示"), QStringLiteral("通道系数保存成功!"));
    } else {
        QLOG_ERROR() << "update channel coefficient from database error";
        QMessageBox::critical(this, QStringLiteral("错误"), QStringLiteral("通道系数保存失败!"));
    }
}

void MainWindow::onFaultOccurred(const QString faultCode)
{
    // 故障处理框: 进行答题判断, 根据判断结果进行故障上报
    DlgFault dlgFault(faultCode);
    int result = dlgFault.exec();
    emit sigFaultNotify(faultCode, result);

    // 故障处理完成, 仿真模拟继续进行
    m_dataSimuThread->setStepWait(false);
}

void MainWindow::onStepNotify(const QString stepCode)
{
    //qDebug() << "********" << stepCode << GlobalStatic::getTrainStep(stepCode);
    m_frmTestInfo->updateStepInfo(stepCode);
}

void MainWindow::onStepWait(const QString stepName)
{
    // 等待操作提示框
    MsgBox msgBox(stepName);
    if (msgBox.exec() == QDialog::Accepted) {
        m_dataSimuThread->setStepWait(false);
    }
}

void MainWindow::onSendCmd(const QString cmdName)
{
    if (m_opened) {
        QByteArray cmd = GlobalStatic::getCmd(cmdName);
        m_serialPort->clear();
        m_serialPort->write(cmd);
    }
}

void MainWindow::onSubflowFinished(const QString subflowName)
{
    // 控制显示操作的复选框状态可用
    m_frmRealtimeData->changeShowOperation(true);
    // 流程进度完成
    m_simuPoints++;
    qint64 runTime = QDateTime::currentMSecsSinceEpoch() - GlobalStatic::getZeroTimeStamp();
    m_frmTestInfo->finishProgress(m_simuPoints, runTime);
    if (m_testingVisible) {
        QString testing = QStringLiteral("已采集点数:%1;总点数:%2;当前项目运行时间:%3;当前项目理论运行时间:%4").arg(m_simuPoints).arg(m_theoryRunTime / m_simuInterval)
                                                                                                     .arg(QString::number(runTime / 1000., 'f', 3), QString::number(m_theoryRunTime / 1000., 'f', 3));
        m_lblTesting->setText(testing);
    }

    // 关闭存储信号通知
    emit sigCloseStorage();


    // 获取当前选中的树形节点
    QModelIndex modelIndex = ui->tvwFlow->currentIndex();
    QStandardItem* currentItem = m_flowModel->itemFromIndex( modelIndex );
    if (currentItem == nullptr)
        return;

    // 弹框提示测试结束
    MsgBox msgBox(QStringLiteral("%1测试结束").arg(subflowName));
    if (msgBox.exec() == QDialog::Accepted) {
    }

    // 标记使能状态
    AppCore::getCore()->getAction("Flow.Run")->setEnabled(true);
    ui->btnRun->setEnabled(true);
    ui->btnTerminate->setEnabled(false);
}

void MainWindow::onCompleteFinished()
{
    // 如果已经开始了训练考试
    if (GlobalStatic::training()) {
        // 考试结束
        GlobalStatic::setTraining(false);

        // 考核成绩
        emit sigCourseScore();
        // 上报训练结束
        emit sigTrainEnd();

        // 训练考试结束
        m_frmTestInfo->updateTrainEnd();

        // 延时1.2s, 弹框提示测试结束
        QEventLoop eventloop;
        QTimer::singleShot(1200, &eventloop, &QEventLoop::quit);
        eventloop.exec();

        int totalStepCount = GlobalStatic::getTotalStepCount();
        int trainedStepCount = GlobalStatic::getTrainedStepCount();
        short courseScore = static_cast<short>( qCeil(100. / totalStepCount * trainedStepCount) );

        MsgBox msgBox(QStringLiteral("训练结束, 考核成绩: %1分").arg(courseScore));
        if (msgBox.exec() == QDialog::Accepted) {
        }
    }
}

void MainWindow::onCourseTrain(const QString courseNumber)
{
    QString courseNumberString;
    switch (m_level) {
    case 1:
        courseNumberString = "C15101";
        break;
    case 2:
        courseNumberString = "C15102";
        break;
    default:
        break;
    }

    if (QString::compare(courseNumber, courseNumberString) != 0) {
        QLOG_ERROR() << QString("****recv course number is: %1, while current level is %2").arg(courseNumber).arg(m_level);

        QString courseString;
        if (QString::compare(courseNumber, "C15101") == 0) {
            courseString = QStringLiteral("一级伺服测试");
        } else if (QString::compare(courseNumber, "C15102") == 0) {
            courseString = QStringLiteral("二级伺服测试");
        } else {
        }

        QMessageBox::StandardButton button = QMessageBox::question(this, QStringLiteral("注意"), QStringLiteral("当前考试科目是%1,但是连接的是%2级, 请确认是否继续?").arg(courseString).arg(m_level), QMessageBox::Yes | QMessageBox::No);
        if (button == QMessageBox::No)
            qApp->quit();

    }

    // 训练考试开始
    m_frmTestInfo->updateTrainStart();

    MsgBox msgBox(QStringLiteral("请注意: 训练考核已开始!"));
    if (msgBox.exec() == QDialog::Accepted) {
    }
}

